##############
# (*) Kaleidoscope
# By: Allan Spale
# July 2008

import sys
import uuid

class KSEventManagerError(Exception):
    """
    Exception class for KSEventManager

    """
    B_UNKNOWN_EXCEPTION = True
    B_CLASS_EXCEPTION = False

    def __init__(self, st_message):
        self.__st_message = st_message
    def __str__(self):
        return repr(self.__st_message)

    @staticmethod
    def print_and_raise_error(u_message, b_unknown_exception):
        """
        Convenience method for printing and raising an error for this
        particular Exception subclass.

        """
        u_message = u'KSEventManager.py: ' + u_message
        sys.stderr.write(u_message + u'\n')

        if (b_unknown_exception):
            raise Exception(u_message)
        else:
            raise KSEventManagerError(u_message)



class KSEventManager(object):
    """
    Later...this module will be a tasklet that will be fronted by a message queue
    that will be used to process trigger commands so that there might be some
    increase in parallelism. For now, to keep things simple, this will just
    trigger single-threaded callbacks. Co-routines and threads will be
    implemented at a later time when things are more stable.

    For all functions that are to be implemented in a subclass, the function
    calls need to check the event channel callbacks to see if any data must
    be sent on channels AT THE START (i.e. before the function executes) or
    AFTER the function finished executing. The format should be the following:

    def tableFunction(self):
        triggerBeforeFunctionEvents(iBefore_tableFunctionEventID)
         ... tableFunction code ...
        triggerAfterFunctionEvents(iAfter_tableFunctionEventID)
        return whatever

    """

    ###########
    B_BEFORE_FUNCTION_FLAG = True
    B_AFTER_FUNCTION_FLAG = False

    @property
    def b_process_channel(self):
        """
        Flag to indicate if the event channel is being processed. Read-only.

        """
        return self.__b_process_channel


    def i_event_manager_id(self):
        """
        Return the KSEventManager identification number.

        """
        return self.__uuid_owner

    #
    __KSEM_EB_register_to_receive_events = 4
    __KSEM_EB_delete_before_function_event = 5
    __KSEM_EB_delete_after_function_event = 6
    __KSEM_EB_register_before_function_event = 7
    __KSEM_EB_register_after_function_event = 8
    __KSEM_EB_start_processing_messages = 9
    __KSEM_EB_stop_processing_messages = 10
    __KSEM_EB_create_function_event = 11

    __KSEM_EA_register_to_receive_events = 1004
    __KSEM_EA_delete_before_function_event = 1005
    __KSEM_EA_delete_after_function_event = 1006
    __KSEM_EA_register_before_function_event = 1007
    __KSEM_EA_register_after_function_event = 1008
    __KSEM_EA_start_processing_messages = 1009
    __KSEM_EA_stop_processing_messages = 1010
    __KSEM_EA_create_function_event = 1011

    ############


    def __init__(self, uuid_owner_id):
        """
        Prepare the KSEventManager queues and dictionaries. Start the tasklet
        for queue processing.

        """

        if (isinstance(uuid_owner_id, uuid.UUID)):
            self.__uuid_owner = uuid_owner_id
        else:
            KSEventManagerError.print_and_raise_error(u'An integer '
                + u'identifier must be provided to KSEventManager.',
                KSEventManagerError.B_CLASS_EXCEPTION)


        self.__dict_event_callbacks = {} # key: function_id, callback list
        self.__dict_function_name_to_id = {} # convenience dictionary
        self.__dict_id_to_function_name = {}
        self.__b_process_channel = True

        # Create the table lookup for function callback registration;
        # allows for programmer to manually send the event ID when
        # writing the program rather than wasting time on a dictionary lookup.
        # However, one must manually enter the first one or else nothing will
        # work correctly.

        #self.create_function_event(u'create_function_event', 11, 1011)
        self.__dict_event_callbacks[11] = []
        self.__dict_function_name_to_id[
            (KSEventManager.B_BEFORE_FUNCTION_FLAG,
              u'create_function_event')] = 11
        self.__dict_id_to_function_name[11] = u'create_function_event'

        self.__dict_event_callbacks[1011] = []
        self.__dict_function_name_to_id[
            (KSEventManager.B_AFTER_FUNCTION_FLAG,
              u'create_function_event')] = 1011
        self.__dict_id_to_function_name[1011] = u'create_function_event'


        self.create_function_event(u'register_to_receive_events', 4, 1004)
        self.create_function_event(u'delete_before_function_event', 5, 1005)
        self.create_function_event(u'delete_after_function_event', 6, 1006)
        self.create_function_event(u'register_before_function_event', 7, 1007)
        self.create_function_event(u'register_after_function_event', 8, 1008)
        self.create_function_event(u'start_processing_messages', 9, 1009)
        self.create_function_event(u'stop_processing_messages', 10, 1010)


    def register_all_functions_from_class(self, my_class, i_start_val):
        try:
            i_before_val = i_start_val
            i_after_val = i_start_val * 10

            for u_item in my_class.__dict__:
                if callable(my_class.__dict__[u_item]):
                    self.create_function_event(
                        my_class.__dict__[u_item], i_before_val, i_after_val)
                    i_before_val = i_before_val + 1
                    i_after_val = i_after_val + 1

        except Exception:
            KSEventManagerError.print_and_raise_error('The before event for '
                + u'the function could not be created.',
                KSEventManagerError.B_CLASS_EXCEPTION)


    def create_function_event(self, obj_function, i_event_before_id,
                             i_event_after_id):
        """
        Create a new event for other functions to register.
        For functions not already defined, start with 100.

        """
        try:
            #print 'creating event...', st_function_name,
            # before
            if (callable(obj_function)):
                st_function_name = obj_function.__name__
            elif (isinstance(obj_function, unicode)):
                st_function_name = obj_function
            else:
                KSEventManagerError.print_and_raise_error('This function '
                    + u'registration was prohibited because of the data type '
                    + u'of the function provided.',
                    KSEventManagerError.B_CLASS_EXCEPTION)

            self.send_event_message(
                KSEventManager.__KSEM_EB_create_function_event,
                [st_function_name, i_event_before_id, i_event_after_id])

            self.__dict_event_callbacks[i_event_before_id] = []
            self.__dict_function_name_to_id[
                (KSEventManager.B_BEFORE_FUNCTION_FLAG, st_function_name)] \
                = i_event_before_id
            self.__dict_id_to_function_name[i_event_before_id] = \
                st_function_name

        except KeyError:
            KSEventManagerError.print_and_raise_error('The before event for '
                + u'the function %s ' % st_function_name + u' could not be '
                + u'created with the ID #%d.' % i_event_before_id,
                KSEventManagerError.B_CLASS_EXCEPTION)

        try:
            self.__dict_event_callbacks[i_event_after_id] = []
            self.__dict_function_name_to_id[
                (KSEventManager.B_AFTER_FUNCTION_FLAG, st_function_name)] \
                = i_event_after_id
            self.__dict_id_to_function_name[i_event_after_id] = \
                st_function_name
            self.send_event_message(
                KSEventManager.__KSEM_EA_create_function_event, [True])
            #print 'success!'
            return True

        except KeyError:
            KSEventManagerError.print_and_raise_error(
                'The after event for the ' + u'function %s ' % \
                    st_function_name + u'could not be created with the ID ' \
                    + u'#%d.' % i_event_after_id,
                    KSEventManagerError.B_CLASS_EXCEPTION)


    def register_before_function_event(self, st_function_name, fn_callback):
        """
        Supply a Stackless channel for an event to which the KSEventManager can
        send and recieve information. Supply the name of the function for which
        to register. This channel will receive data before the function
        that generated the event begins its execution.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_register_before_function_event,
                [st_function_name, fn_callback])

            i_event_id = self.__dict_function_name_to_id[
                (KSEventManager.B_BEFORE_FUNCTION_FLAG, st_function_name)]
            self.__dict_event_callbacks[i_event_id].append(fn_callback)

            self.send_event_message(
                KSEventManager.__KSEM_EA_register_before_function_event, [True])
            return True

        except KeyError:
            # Silently fail to prevent differences in KSColumn subclasses; but
            # return false to indicate the failure rather than raising an
            # exception that complicates things.
            return False

            #KSEventManagerError.print_and_raise_error(u'The function named %s '
            #    % st_function_name + u'cannot register a "before event" '
            #    + u'callback because it does not exist.',
            #    KSEventManagerError.B_CLASS_EXCEPTION)

        except Exception:
            KSEventManagerError.print_and_raise_error(u'The function named %s '
                % st_function_name + u'could not be added to the "before '
                + u' event" channel list.',
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def register_after_function_event(self, st_function_name, fn_callback):
        """
        Supply a Stackless channel to which the KSEventManager can send
        and recieve information. Supply the name of the function for which
        to register. This channel will receive data after the function
        that generated the event completes its execution.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_register_after_function_event,
                [st_function_name, fn_callback])

            i_event_id = self.__dict_function_name_to_id[
                (KSEventManager.B_AFTER_FUNCTION_FLAG, st_function_name)]
            self.__dict_event_callbacks[i_event_id].append(fn_callback)

            self.send_event_message(
                KSEventManager.__KSEM_EA_register_after_function_event, [True])
            return True

        except KeyError:
            return False
            #KSEventManagerError.print_and_raise_error(u'The function named %s '
            #    % st_function_name + u'cannot register an "after event" '
            #    + u'callback because it does not exist.',
            #    KSEventManagerError.B_CLASS_EXCEPTION)

        except Exception:
            KSEventManagerError.print_and_raise_error(u'The function named %s '
                % st_function_name + u'could not be added to the "after '
                + u'event" channel list.',
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    """
    def register_to_receive_events(self, st_function_name, fn_callback):
        #Supply a function callback that will receive the events as they
        #appear in the event queue. Return the ID of the KSEventManager.
        #
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_register_to_receive_events,
                [st_function_name]) # before event
            self.__list_subclass_function_callback.append(fn_callback)
            self.send_event_message(
                KSEventManager.__KSEM_EA_register_to_receive_events,
                [self.__uuid_owner]) # after event
            return self.__uuid_owner
        except Exception:
            KSEventManagerError.print_and_raise_error(u'The function could '
                + u'not register to receive events from %s due to an unknown '
                + u'error.' % st_function_name,
                KSEventManagerError.B_UNKNOWN_EXCEPTION)
    """


    def delete_before_function_event(self, st_function_name):
        """
        Delete the function event generator that occurs before the name
        given by st_function_name begins execution.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_delete_before_function_event,
                [st_function_name]) # before event
            self.__delete_function_event(self,
                KSEventManager.B_BEFORE_FUNCTION_FLAG, st_function_name)
            self.send_event_message(
                KSEventManager.__KSEM_EA_delete_before_function_event, [True])
            return True

        except Exception:
            KSEventManagerError.print_and_raise_error(u'The "before function '
                + u'event" named %s could not be deleted because of an '
                + u'unknown error.' % st_function_name,
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def delete_after_function_event(self, st_function_name):
        """
        Delete the function event generator that occurs before the name
        given by st_function_name completes its execution.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_delete_after_function_event,
                [st_function_name]) # before event
            self.__delete_function_event(self,
                KSEventManager.B_AFTER_FUNCTION_FLAG, st_function_name)
            self.send_event_message(
                KSEventManager.__KSEM_EA_delete_after_function_event, [True])
            return True
        except Exception:
            KSEventManagerError.print_and_raise_error(u'The "after function '
                + u'event" named %s could not be deleted because of an '
                + u'unknown error.' % st_function_name,
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def start_processing_messages(self):
        """
        Set the flag b_process_channel to True to begin processing event
        messages in the Stackless channel.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_start_processing_messages,
                [self.__b_process_channel]) # before

            if (self.__b_process_channel == False):
                self.__b_process_channel = True

            self.send_event_message(
                KSEventManager.__KSEM_EA_start_processing_messages,
                [self.__b_process_channel]) # after
            return self.__b_process_channel

        except Exception:
            KSEventManagerError.print_and_raise_error(u'An unknown error '
                + u'prevented the setting of processing messages to '
                + u'be applied.', KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def stop_processing_messages(self):
        """
        Set the flag b_process_channel to False to stop processing event
        messages in the Stackless channel.

        """
        try:
            self.send_event_message(
                KSEventManager.__KSEM_EB_stop_processing_messages,
                [self.__b_process_channel]) # before
            if (self.__b_process_channel):
                self.__b_process_channel = False
            self.send_event_message(
                KSEventManager.__KSEM_EA_stop_processing_messages,
                [self.__b_process_channel]) # after
            return self.__b_process_channel

        except Exception:
            KSEventManagerError.print_and_raise_error(u'An unknown error '
                + u'prevented the setting of blocking messages to be applied.',
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def get_event_id(self, st_function_name):
        """
        Given the name of the function in st_function_name, return its
        equivalent event ID.

        """
        try:
            return self.__dict_function_name_to_id[st_function_name]

        except KeyError:
            KSEventManagerError.print_and_raise_error(u'The event ID for '
                + u'the function %s' % st_function_name + u'does not exist.',
                KSEventManagerError.B_CLASS_EXCEPTION)

    def get_function_name_from_event(self, i_event_id):
        """
        Given the event ID, return the name of the function that generates
        this event.

        """
        try:
            st_function_name = self.__dict_id_to_function_name[i_event_id]
            return st_function_name
        except KeyError:
            KSEventManagerError.print_and_raise_error(
                u'Event %d does not exist.' % i_event_id,
                KSEventManagerError.B_CLASS_EXCEPTION)


    def send_before_event_message(self, fn_current, list_data):
        """
        Send the event that is created when a function is called but does not
        begin execution. The items in list_data are likely to be the parameters
        that were sent to the original function. For the most part, the purpose
        of this event is to pass the parameters of the original function to
        the callbacks. However, these callback functions should not fully
        execute because the certainty of how the event-generating function
        will complete its execution.

        """
        try:
            #print 'before fn: ', fn_current.__name__, ' val: ', \
            #    self.__dict_function_name_to_id[
            #        (KSEventManager.B_BEFORE_FUNCTION_FLAG,
            #         fn_current.__name__)]

            self.send_event_message(self.__dict_function_name_to_id[
                (KSEventManager.B_BEFORE_FUNCTION_FLAG, fn_current.__name__)],
                list_data)
        except KeyError:
            KSEventManagerError.print_and_raise_error(
                u'The "before event" %s does not exist.' % fn_current.__name__,
                KSEventManagerError.B_CLASS_EXCEPTION)

    def send_after_event_message(self, fn_current, list_data):
        """
        Send the event that is created after a function finishes execution. The
        items in list_data are likely to be the items returned by the function.
        The return values should validate that the function either successfully
        executed or failed. Then the callback functions already registered can
        perform their tasks appropriately.

        """
        try:
            #print 'after fn: ', fn_current.__name__, ' val: ', \
            #    self.__dict_function_name_to_id[
            #        (KSEventManager.B_AFTER_FUNCTION_FLAG, fn_current.__name__)]

            self.send_event_message(self.__dict_function_name_to_id[
                (KSEventManager.B_AFTER_FUNCTION_FLAG, fn_current.__name__)],
                list_data)
        except KeyError:
            KSEventManagerError.print_and_raise_error(
                u'The "after event" %s does not exist.' % fn_current.__name__,
                KSEventManagerError.B_CLASS_EXCEPTION)



    def send_event_message(self, i_event_id, list_data):
        """
        Send the KSEventManager ID, the event ID, and the data objects
        contained in list_data to the each Stackless channel that has
        registered to be notified of the event.

        """
        try:
            if (self.__b_process_channel):
                for fn_callback in self.__dict_event_callbacks[i_event_id]:
                    print 'sending...'
                    print 'manager: %d, event: %d, data:' % (self.__uuid_owner,
                        i_event_id), list_data
                    fn_callback([self.__uuid_owner, i_event_id, list_data])
            else:
                pass

        except KeyError:
            KSEventManagerError.print_and_raise_error(u'Could not send an '
                + u'event for the function %s.'
                % self.__dict_id_to_function_name[i_event_id],
                KSEventManagerError.B_CLASS_EXCEPTION)

        except Exception:
            KSEventManagerError.print_and_raise_error(u'An unknown error '
                + u'prevented the message from function %s from being sent.'
                % self.__dict_id_to_function_name[i_event_id],
                KSEventManagerError.B_UNKNOWN_EXCEPTION)


    def __delete_function_event(self, b_time_flag, st_function_name):
        """
        Private method that eliminates some code duplication involved with
        deleting the triggering of a function event.

        """
        try:
            i_event_id = self.__dict_function_name_to_id[
                (b_time_flag, st_function_name)]
            del self.__dict_function_name_to_id[
                (b_time_flag, st_function_name)]
            del self.__dict_id_to_function_name[i_event_id]

        except Exception:
            KSEventManagerError.print_and_raise_error(u'The function named %s '
                % st_function_name + u'cannot be deleted.',
                KSEventManagerError.B_UNKNOWN_EXCEPTION)